Tightening Pod/Container Security for All Workloads#100
Conversation
There was a problem hiding this comment.
Pull request overview
This PR tightens the default Pod/Container security contexts for Graylog, DataNode, and GeoIP update jobs, and makes those security settings configurable via values.yaml so operators can adjust or opt out as needed.
Changes:
- Added values-driven
podSecurityContext,initContainerSecurityContext, andcontainerSecurityContextdefaults for Graylog and DataNode. - Updated StatefulSet templates to render the new values-driven security contexts, and added a DataNode
data-chowninit container. - Updated GeoIP CronJob/Job rendering to accept Pod/Container security contexts via the shared helper.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| charts/graylog/values.yaml | Introduces configurable default security contexts (and initContainer image config) for Graylog and DataNode. |
| charts/graylog/templates/workload/statefulsets/graylog.yaml | Renders new values-driven pod/container/init-container security contexts for Graylog workload and plugin-copy init containers. |
| charts/graylog/templates/workload/statefulsets/datanode.yaml | Adds values-driven pod/container/init-container security contexts plus a data-chown init container for PVC preparation. |
| charts/graylog/templates/workload/cronjobs/geoip.yaml | Passes security contexts into the GeoIP JobSpec helper. |
| charts/graylog/templates/_helpers.tpl | Extends GeoIP JobSpec helper to optionally render pod/container security contexts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Since the datanode container currently must run with root privileges, the data-chown init container becomes redundant and adds complexity. The datanode entrypoint unconditionally creates and chowns the data directories as root before dropping privileges, so the busybox data-chown init container only duplicated that work without letting the main container shed any capabilities. Removing it also drops the busybox:latest public-registry pull. Also removes the now-unused datanode.initContainerImage and datanode.initContainerSecurityContext values. The main container's security context is unchanged. Ref: https://github.com/Graylog2/graylog-docker/blob/937a318/docker/datanode/entrypoint.sh Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>
…ainers
add a conditional rendering for the security context of `copy-plugin-` init containers based on `.Values.graylog.initContainerSecurityContext`: if `initContainerSecurityContext: {}` no security context is merged, otherwise the intended merge takes place
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Guard the pod/init/container security contexts added for the graylog and datanode workloads and the geoip update CronJob (issue #70): - graylog StatefulSet: default pod/init/container contexts, copy-data init container has no NET_BIND_SERVICE, graylog-app keeps it, copy-plugin init containers are forced readOnlyRootFilesystem, and the opt-out path. - datanode StatefulSet: new suite covering the pod context and the startup capabilities on the main container, plus opt-out. - geoip CronJob: the graylog pod/container contexts thread through the graylog.geoip.job.spec helper's positional args, plus opt-out. Opt-out is asserted with `null`, not `{}`: an empty map is coalesced back to the chart defaults by Helm, so only null actually clears the field. Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>
Add podSecurityContext / initContainerSecurityContext / containerSecurityContext to the graylog block and podSecurityContext / containerSecurityContext to the datanode block, matching the values.yaml added for issue #70. Typed as ["object", "null"] so the null opt-out validates — an empty map ({}) is coalesced back to the chart defaults by Helm, so null is the only value that actually clears the field. Co-Authored-By: Claude Opus 4.8 (1M context)[EMAIL_ADDRESS_REDACTED]om>
The copy-plugin securityContext used merge (dict "readOnlyRootFilesystem" true) over the user's initContainerSecurityContext. Because mergo treats false as a zero-value, a user setting readOnlyRootFilesystem: false could never override the true default. Default it with hasKey instead: only inject readOnlyRootFilesystem: true when the user hasn't set the key, so an explicit false is preserved. This also lets the block use `with` on the raw value, which self-skips on the null opt-out, dropping the separate if-guard. Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>
|
Made a few changes:
|
Add a Hardened Environments section to the README covering the PSA exemptions the datanode and MongoDB operator currently require (or BYO MongoDB), and clarify the fsGroup comment in values.yaml. Allow maxmindGeoIp.accountId to be an integer in values.schema.json (the template already casts it), so a bare --set works without --set-string. Warn in NOTES.txt when graylog.podSecurityContext.fsGroup is unset, since Graylog runs non-root and needs it to write its volume on storage that enforces ownership (e.g. EBS). Co-Authored-By: Claude Opus 4.8 (1M context) <[EMAIL_ADDRESS_REDACTED]>
|
Did a validation run on EKS: Environment
Test matrix
Confirmed claims
With the cap present, Graylog runs clean as uid=1100, effective caps 0 at runtime (cap only needed for the file-cap exec transition).
With the caps, the container starts as root (PID 1) and the workload drops to
The geoip container inherits One minor "regression"The opt-out example sets This is a new failure mode: the pre-hardening chart hardcoded Solution: since Good to merge! |
Summary
Locking down default pod and container security contexts, as far as the application currently allows, and exposing the configuration to the
values.yamlDetails
and it's init container's security context's and moving them into user configurable valuesWhat this does
Moves security contexts out of hardcoded template values and into
values.yamlso they're configurable, and tightens the defaults for both Graylog and Datanode.podSecurityContextandcontainerSecurityContextare now values-driven for both workloadsinitContainerSecurityContext— they don't needNET_BIND_SERVICEsince they never start the JVMcopy-plugin-*init containers getreadOnlyRootFilesystem: true(they only write to an emptyDir)Datanode gets adata-chowninit container (busybox) that handles ownership setup on the PVC before the main container starts. The image is configurable viadatanode.initContainerImagefor private registry supportseccompProfile: RuntimeDefaultapplied across all containersallowPrivilegeEscalation: falseandcapabilities: drop: ALLon everythingWhy some things couldn't be locked down further
Graylog —
NET_BIND_SERVICEhas to stay. The JDK binary in the image hascap_net_bind_service=epset as a file capability. Withno_new_privs=1(whichallowPrivilegeEscalation: falsesets), executing a binary that would gain capabilities not already in the process's permitted set fails withEPERM. Pre-addingNET_BIND_SERVICEto the container's permitted set means the file capability doesn't grant anything new, so the exec works.Datanode — has to start as root. This is common with Opensearch in Kubernetes. The entrypoint does
chown -R graylog:graylog /var/lib/graylog-datanodethenexec setpriv --reuid=graylog --regid=graylog --init-groupsunconditionally. That requiresCHOWN,DAC_OVERRIDE,SETUID, andSETGID. We tried moving the chown to the init container to strip those from the main container, but the entrypoint still runs its ownchown -Ron startup — and root withoutCAP_CHOWNcan't chown files owned by uid 999. We also tried settingrunAsUser: 999directly, but the container runtime clears the effective capability set when it switches from root to non-root, soSETGIDends up in the bounding set but not effective —setpriv --init-groupsstill fails. Aftersetprivruns the process has uid 999 and no capabilities, so the running application is fine, just the startup window needs them.readOnlyRootFilesystem— not set on the main containers. Both the Graylog JVM and the OpenSearch-based datanode write to the container filesystem at runtime and would need a full audit + emptyDir mounts to enable this safely.Opting out
If the security contexts cause issues in your environment, each field is fully overridable. To disable them entirely, set the value to
null— note that{}does not work: Helm coalesces an empty map back to the chart defaults, so the default context still renders.nullis required to actually clear the field.Linked issues
PR Checklist
Please check the items that apply to your change.
Testing Checklist
Static Validation
helm lint ./charts/grayloghelm template graylog ./charts/graylog --validateInstallation
helm install graylog ./charts/graylogkubectl rollout status statefulset/grayloghelm test graylogFunctional (if applicable)
Upgrade (if applicable)
Specific to this PR
Notes for reviewers